package mt.filter;

import static java.util.concurrent.TimeUnit.SECONDS;

import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;

import mt.Order;
import mt.comm.ServerComm;
import mt.comm.ServerSideMessage;

/**
 * 
 * @author Group11
 * 
 */
public class AnalyticsFilter implements ServerComm, Runnable {

	private ServerComm decoratedServerComm;
	private int numberOfOrderToSale = 0;
	private int numberOfOrderToBuy = 0;
	// private Calendar calendar;

	private final ScheduledExecutorService scheduler = Executors
			.newScheduledThreadPool(1);

	private HashMap<ServerSideMessage, Long> messages = new HashMap<ServerSideMessage, Long>();
	private Queue<ServerSideMessage> queueTenSec = new LinkedList<ServerSideMessage>();
	private HashMap<String, Long> blacklist = new HashMap<String, Long>();
	private PrintWriter writer;

	/**
	 * Call by the constructor schedule an to event to calculate statistics that
	 * will start in 60 second with an period of 60 second calling
	 * {@link #estatistica()}
	 * 
	 */

	private void inicializarEstatistica() {

		final ScheduledFuture<?> fazerEstatistica = scheduler
				.scheduleAtFixedRate(this, 60, 60, SECONDS);
	}

	/**
	 * print the statistics on the console,on the file and clear messages
	 */
	protected synchronized void estatistica() {

		PrintWriter out = null;
		try {
			out = new PrintWriter(new BufferedWriter(new FileWriter(
					"estatistica.txt", true)));
		} catch (IOException e) {
			e.printStackTrace();
		}
		String buy = "" + Calendar.getInstance().getTime()
				+ "\nOrder to sells: " + numberOfOrderToBuy + " in "
				+ messages.size() + " orders.";
		String sell = "" + "Order to buy: " + numberOfOrderToSale + " in "
				+ messages.size() + " orders.";
		System.out.println(buy);
		System.out.println(sell);

		out.println(buy);
		out.println(sell);
		out.close();
		numberOfOrderToBuy = 0;
		numberOfOrderToSale = 0;

	}

	/**
	 * construct the Analytics Filter
	 * 
	 * @param serverCommToDecorate
	 *            - {@link ServerComm} to decorate
	 */

	public AnalyticsFilter(ServerComm serverCommToDecorate) {
		decoratedServerComm = serverCommToDecorate;
		inicializarEstatistica();

		String start = "-----------------------------------------------------\n------Micro Trader Server starts - Time: "
				+ Calendar.getInstance().getTime();
		System.out.println();
		System.out.println(start);
		try {
			writer = new PrintWriter("estatistica.txt", "UTF-8");
			writer.println(start);
			writer.close();

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void start() {
		decoratedServerComm.start();
	}

	@Override
	public ServerSideMessage getNextMessage() {
		ServerSideMessage message = decoratedServerComm.getNextMessage();

		if (message != null) {
			if (message.getType() == ServerSideMessage.Type.CONNECTED
					&& blacklist.containsKey(message.getSenderNickname())) {

				if (Calendar.getInstance().getTimeInMillis()
						- blacklist.get(message.getSenderNickname()) < 30000) {
					decoratedServerComm.disconnectClient(message
							.getSenderNickname());
					return getNextMessage();
				} else
					blacklist.remove(message.getSenderNickname());
			}
			dealWithMessage(message);
		}
		return message;
	}

	/**
	 * Verify if the order is to buy or sell and call
	 * {@link #detetectAutomaticOrder()} for detecting automatic order made by
	 * the client
	 * 
	 * @param message
	 *            - message to get the order
	 */
	private synchronized void dealWithMessage(ServerSideMessage message) {
		if (message.getType() == mt.comm.ServerSideMessage.Type.NEW_ORDER) {
			messages.put(message, Calendar.getInstance().getTimeInMillis());
			queueTenSec.add(message);

			if (message.getOrder().isBuyOrder())
				numberOfOrderToBuy++;
			else
				numberOfOrderToSale++;

			detetectAutomaticOrder();

		}
	}

	/**
	 * Detects automatic orders send from the client calling the
	 * {@link #countNumberOfMessageForClient()}
	 */
	private void detetectAutomaticOrder() {

		if (queueTenSec.size() < 3
				|| Calendar.getInstance().getTimeInMillis()
						- messages.get(queueTenSec.element()) > 10000) {
			while (!queueTenSec.isEmpty()
					&& Calendar.getInstance().getTimeInMillis()
							- messages.get(queueTenSec.element()) > 10000) {
				queueTenSec.remove();
			}

		} else {
			countNumberOfMessageForClient();
		}

	}

	/**
	 * count if a client send three order in less then ten second
	 */
	private void countNumberOfMessageForClient() {

		HashMap<String, Integer> numeroDeMessage = new HashMap<String, Integer>();

		for (ServerSideMessage serverSideMessage : queueTenSec) {
			String nickname = serverSideMessage.getSenderNickname();
			if (!numeroDeMessage.containsKey(nickname)) {
				numeroDeMessage.put(nickname, 1);
			} else {
				int value = numeroDeMessage.get(nickname) + 1;
				numeroDeMessage.put(nickname, value);
			}
		}

		for (Entry<String, Integer> numMes : numeroDeMessage.entrySet()) {
			if (numMes.getValue() >= 3) {
				disconnectClient(numMes.getKey());
				blacklist.put(numMes.getKey(), Calendar.getInstance()
						.getTimeInMillis());

			}

		}
	}

	@Override
	public boolean hasNextMessage() {
		return decoratedServerComm.hasNextMessage();
	}

	@Override
	public void sendOrder(String receiversNickname, Order order) {
		decoratedServerComm.sendOrder(receiversNickname, order);
	}

	@Override
	public void sendError(String toNickname, String error) {
		decoratedServerComm.sendError(toNickname, error);
	}

	@Override
	public boolean clientIsConnected(String nickname) {
		return decoratedServerComm.clientIsConnected(nickname);
	}

	@Override
	public void disconnectClient(String nickname) {
		decoratedServerComm.disconnectClient(nickname);
	}

	@Override
	public void run() {

		estatistica();
	}

}
